﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml.Linq;
using MyVocabulary.App_Code;
using MyVocabulary.Popup;

namespace MyVocabulary
{
    public partial class Vocabulary : Form
    {       
        #region Form messages

        protected static class FormMessages
        {
            public const string FormTitle = "Vocabulary File - {0}";

            public const string ErrorMessageTitle = "Error";
            public const string FileNotFound = "The vocabulary file could not be fould";
            public const string ErrorLoadingFile = "An error occured while loading the vocabulary file. Details: {0}.";
            public const string ErrorSavingFile = "An error occured while saving the vocabulary file. Details: {0}.";
            public const string MultipleSelectionForEdit = "Please select only one word to edit";
            
            public const string ReloadFormTitle = "Reload";
            public const string ConfirmReloadQuestion =
                "Doing a reload will cause any unsaved data to be lost. Are you sure you want to reload the file?";

            public const string WindowClosingTitle = "Window Close Confirmation";
            public const string ConfirmWindowClosingQuestion =
                "There are unsaved modifications that will be lost if you close the window. Do you want to close it anyway?";

            public const string DeleteFormTitle = "Delete";
            public const string ConfirmWordRemovalQuestion =
                "Do you really want to remove the selected word(s) from the vocabulary?";

            public const string NoWordToTestTitle = "Empty vocabulary";
            public const string NoWordToTestMsg = "You must add words to the vocabulary before being able to start a test.";

            public const string ErrorExportingFile = "An error occured while exporting the list to a .txt file. Details: {0}.";

            public const string SaveSuccessTitle = "Save";
            public const string SaveSuccessMsg = "The vocabulary was saved successfully.";

            public const string HtmlFileStructure = @"
                <!doctype html>
                <html>
                <head>
                    <meta http-equiv=""content-type"" content=""text/html; charset=UTF-8"" />
                    <title>My Vocabulary Export</title>
                    <style type=""text/css"">
                        table.tbl
                        {
                            border-width: 1px;
                            border-spacing: 0px;
                            border-style: solid;
                            border-color: gray;
                            border-collapse: collapse;
                        }
                        table.tbl td
                        {
                            border-width: 1px;
                            padding: 2px;
                            border-style: solid;
                            border-color: gray;
                        }                                               
                        .highlightedText     
                        {   
        	                color: Navy;
                        }
                    </style>
                </head>
                <body>
                    {BODY_CONTENT}
                </body>
                </html>";
        }

        #endregion

        #region Fields & Properties

        public string FilePath{ get; set;}
        public string FileName { set { Text = string.Format(FormMessages.FormTitle, value); } }

        /// <summary>
        /// Flag used to see if the user modified the data that was initially loaded / last saved
        /// </summary>
        protected bool hasDataChanges;

        protected bool errorLoadingFile;
        /// <summary>
        /// True if an error occured while loading the vocabulary
        /// </summary>
        public bool ErrorLoadingFile { get { return errorLoadingFile; } }

        #endregion

        #region Constructors

        public Vocabulary(string FilePath)
        {            
            InitializeComponent();

            this.FilePath = FilePath;
            LoadFile();
        }

        protected void LoadFile()
        {
            errorLoadingFile = false;
            lvWords.Items.Clear();
            hasDataChanges = false;
            btnTest.Enabled = false;
            btnSort.Enabled = false;
            btnSortDesc.Enabled = false;

            //Check if the file exists
            if (!File.Exists(FilePath))
            {
                new CustomMessageBox(FormMessages.FileNotFound, FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.Cancel).ShowDialog(this);
                errorLoadingFile = true;
                return;
            }

            //Read the file and add the values into the list view          
            try
            {
                var doc = XDocument.Load(FilePath);
                var element = doc.Root;
                if (element == null || !element.HasElements)
                    return;

                foreach (var e in element.Elements(VocabularyWordConst.VocabularyWord))
                {
                    if (e.HasElements)
                    {
                        var item = new CustomListViewItem();
                        var vocab = new VocabularyWord();
                        item.AdditionalInfo[VocabularyWordConst.VocabularyWord] = vocab;
                        foreach (var col in e.Elements())
                        {
                            switch (col.Name.LocalName)
                            {
                                case VocabularyWordConst.Word:
                                    vocab.Word = col.Value;
                                    break;
                                case VocabularyWordConst.Translation:
                                    vocab.Translation = col.Value;
                                    break;
                                case VocabularyWordConst.PartOfSpeech:
                                    vocab.PartOfSpeech = col.Value;
                                    break;
                                case VocabularyWordConst.Examples:
                                    vocab.Examples = col.Value;
                                    break;
                                case VocabularyWordConst.Remarks:
                                    vocab.Remarks = col.Value;
                                    break;
                            }                            
                        }
                        if (!string.IsNullOrEmpty(vocab.Word.Trim()) && !string.IsNullOrEmpty(vocab.Translation.Trim()))
                        {
                            item.Text = vocab.Word;
                            item.SubItems.Add(new ListViewItem.ListViewSubItem(item, vocab.Translation));
                            lvWords.Items.Add(item);
                        }
                    }
                }
            }
            catch(Exception exp)
            {
                new CustomMessageBox(string.Format(FormMessages.ErrorLoadingFile, exp.Message), FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.Cancel).ShowDialog(this);
                errorLoadingFile = true;
                return;
            }       
            finally
            {
                btnTest.Enabled = btnSort.Enabled = btnSortDesc.Enabled = lvWords.Items.Count > 0;
            }
        }

        #endregion

        #region lvWords

        private void lvWords_DoubleClick(object sender, EventArgs e)
        {
            btnEdit_Click(sender, e);
        }

        private void lvWords_SelectedIndexChanged(object sender, EventArgs e)
        {
            var enabled = lvWords.SelectedItems.Count > 0;
            btnEdit.Enabled = btnDelete.Enabled = enabled;
        }

        private void cvWords_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            var selectedItems = (lvWords.SelectedItems.Count > 0);
            var hasItems = (lvWords.Items.Count > 0);

            cvWords.Items["testVocabulary"].Visible =
                cvWords.Items["sortWords"].Visible = cvWords.Items["sortWordsDesc"].Visible = !selectedItems;

            cvWords.Items["testVocabulary"].Enabled =
                cvWords.Items["sortWords"].Enabled = cvWords.Items["sortWordsDesc"].Enabled = hasItems;

            cvWords.Items["editWord"].Visible = cvWords.Items["deleteWord"].Visible = selectedItems;
        }

        #region Drag&Drop

        private void lvWords_ItemDrag(object sender, ItemDragEventArgs e)
        {
            lvWords.DoDragDrop("lvWords", DragDropEffects.Move);
        }

        private void lvWords_DragEnter(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            var text = (string)e.Data.GetData(typeof(string));
            e.Effect = text.CompareTo("lvWords") == 0 ? DragDropEffects.Move : DragDropEffects.None;
        }

        private void lvWords_DragOver(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            var cp = lvWords.PointToClient(new Point(e.X, e.Y));
            var hoverItem = lvWords.GetItemAt(cp.X, cp.Y);
            if (hoverItem == null)
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            foreach (CustomListViewItem moveItem in lvWords.SelectedItems)
            {
                if (moveItem.Index == hoverItem.Index)
                {
                    e.Effect = DragDropEffects.None;
                    hoverItem.EnsureVisible();
                    return;
                }
            }
            var text = (String)e.Data.GetData("lvWords".GetType());
            if (text.CompareTo("lvWords") == 0)
            {
                e.Effect = DragDropEffects.Move;
                hoverItem.EnsureVisible();
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        private void lvWords_DragDrop(object sender, DragEventArgs e)
        {
            if (lvWords.SelectedItems.Count == 0)
            {
                return;
            }
            var cp = lvWords.PointToClient(new Point(e.X, e.Y));
            var dragToItem = lvWords.GetItemAt(cp.X, cp.Y);
            if (dragToItem == null)
            {
                return;
            }
            var dropIndex = dragToItem.Index;
            if (dropIndex > lvWords.SelectedItems[0].Index)
            {
                dropIndex++;
            }
            var insertItems = new ArrayList(lvWords.SelectedItems.Count);
            foreach (CustomListViewItem item in lvWords.SelectedItems)
            {
                var newItem = (CustomListViewItem)item.Clone();
                newItem.AdditionalInfo = item.AdditionalInfo;
                insertItems.Add(newItem);
            }
            for (var i = insertItems.Count - 1; i >= 0; i--)
            {
                var insertItem = (CustomListViewItem)insertItems[i];
                lvWords.Items.Insert(dropIndex, insertItem);
            }
            foreach (CustomListViewItem removeItem in lvWords.SelectedItems)
            {
                lvWords.Items.Remove(removeItem);
            }
        }

        #endregion

        #endregion
        
        #region Commands

        private void btnSave_Click(object sender, EventArgs e)
        {
            try
            {
                var doc = XDocument.Load(FilePath);
                var element = doc.Root;
                if (element == null)
                {
                    element = new XElement("DocumentElement");
                    doc.AddFirst(element);
                }
                
                element.Elements().Remove();
                foreach (CustomListViewItem item in lvWords.Items)
                {
                    var vocab = (VocabularyWord)item.AdditionalInfo[VocabularyWordConst.VocabularyWord];
                    var row = new XElement(VocabularyWordConst.VocabularyWord);
                    row.Add(new XElement(VocabularyWordConst.Word, vocab.Word));
                    row.Add(new XElement(VocabularyWordConst.Translation, vocab.Translation));
                    row.Add(new XElement(VocabularyWordConst.PartOfSpeech, vocab.PartOfSpeech));
                    row.Add(new XElement(VocabularyWordConst.Examples, vocab.Examples));
                    row.Add(new XElement(VocabularyWordConst.Remarks, vocab.Remarks));
                    element.Add(row);                    
                }
                              
                //Save the modifications
                doc.Save(FilePath);

                hasDataChanges = false;

                new CustomMessageBox(FormMessages.SaveSuccessMsg, FormMessages.SaveSuccessTitle, CustomMessageBoxButtons.OK).ShowDialog(this);
            }
            catch (Exception exp)
            {
                new CustomMessageBox(string.Format(FormMessages.ErrorSavingFile, exp.Message), FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.Cancel).ShowDialog(this);
                return;
            }
        }

        private void btnExport_Click(object sender, EventArgs e)
        {
            var file = new FileInfo(FilePath);
            if(file.Exists)
            {
                saveFileDialog1.InitialDirectory = file.DirectoryName;
                saveFileDialog1.FileName = "Export" + (new Regex("\\.xml$").Replace(file.Name, string.Empty));
            }

            if(saveFileDialog1.ShowDialog() == DialogResult.OK)
            {                
                try
                {
                   //create the file
                    var CreatedFile = new FileInfo(saveFileDialog1.FileName);
                    var fs = CreatedFile.Create();
                    var writer = new StreamWriter(fs);

                    if (CreatedFile.Extension.Equals(".html") || CreatedFile.Extension.Equals(".htm"))
                    {
                        #region Export to a HTML file

                        var sb = new StringBuilder();
                        sb.AppendLine("<table class=\"tbl\">");
                        foreach (CustomListViewItem item in lvWords.Items)
                        { 
                            var word = (VocabularyWord)item.AdditionalInfo[VocabularyWordConst.VocabularyWord];
                            var rowSpan = ((string.IsNullOrEmpty(word.PartOfSpeech.Trim())) ? 0 : 1) +
                                          ((string.IsNullOrEmpty(word.Examples.Trim())) ? 0 : 1) +
                                          ((string.IsNullOrEmpty(word.Remarks.Trim())) ? 0 : 1) + 1;
                            sb.AppendLine(
                                string.Format(
                                    "<tr><td rowspan=\"{0}\" class=\"highlightedText\">{1}</td><td colspan=\"2\">{2}</td></tr>",
                                    rowSpan, System.Web.HttpUtility.HtmlEncode(word.Word),
                                    System.Web.HttpUtility.HtmlEncode(word.Translation)));
                            
                            if (!string.IsNullOrEmpty(word.PartOfSpeech.Trim()))
                                sb.AppendLine(string.Format("<tr><td>Part Of Speech:</td><td>{0}</td></tr>", System.Web.HttpUtility.HtmlEncode(word.PartOfSpeech)));
                            if (!string.IsNullOrEmpty(word.Examples.Trim()))
                                sb.AppendLine(string.Format("<tr><td>Examples:</td><td>{0}</td></tr>", NewLinesInHTML(word.Examples)));
                            if (!string.IsNullOrEmpty(word.Remarks.Trim()))
                                sb.AppendLine(string.Format("<tr><td>Remarks:</td><td>{0}</td></tr>", NewLinesInHTML(word.Remarks)));
                        }
                        sb.AppendLine("</table>");

                        writer.Write(FormMessages.HtmlFileStructure.Replace("{BODY_CONTENT}", sb.ToString()));

                        #endregion
                    }
                    else
                    {
                        #region Export to a TXT file
                        
                        foreach (CustomListViewItem item in lvWords.Items)
                        {
                            var word = (VocabularyWord)item.AdditionalInfo[VocabularyWordConst.VocabularyWord];
                            writer.WriteLine("{0} = {1}", word.Word, word.Translation);
                            if (!string.IsNullOrEmpty(word.PartOfSpeech.Trim()))
                                WriteLinesToWriter(writer, string.Format("Part Of Speech: {0}", word.PartOfSpeech));                                
                            if (!string.IsNullOrEmpty(word.Examples.Trim()))
                                WriteLinesToWriter(writer, string.Format("Examples: {0}", word.Examples));                                
                            if (!string.IsNullOrEmpty(word.Remarks.Trim()))
                                WriteLinesToWriter(writer, string.Format("Remarks: {0}", word.Remarks));
                            writer.WriteLine("----------------------------------------------------------------------------------------------");
                            writer.WriteLine(string.Empty);
                        }

                        #endregion
                    }

                    writer.Flush();
                    writer.Close();
                    fs.Close();
                }
                catch (Exception exp)
                {
                    (new CustomMessageBox(string.Format(FormMessages.ErrorExportingFile, exp.Message),
                                          FormMessages.ErrorMessageTitle, CustomMessageBoxButtons.OK)).ShowDialog(this);
                }
            }
        }

        /// <summary>
        /// Make sure that \n or \r\n chars will appear as new lines
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="text"></param>
        protected static void WriteLinesToWriter(StreamWriter writer, string text)
        {
            var lines = (new Regex(@"\n|\r\n").Split(text));
            foreach (var line in lines)
            {
                writer.WriteLine(line);
            }
        }

        /// <summary>
        /// Make sure that \n or \r\n chars will appear as new lines in HTML files
        /// </summary>
        /// <param name="text"></param>
        protected static string NewLinesInHTML(string text)
        {
            return (new Regex(@"\n|\r\n").Replace(System.Web.HttpUtility.HtmlEncode(text), "<br />"));
        }

        private void btnReload_Click(object sender, EventArgs e)
        {
            if (hasDataChanges == false || (new CustomMessageBox(FormMessages.ConfirmReloadQuestion, FormMessages.ReloadFormTitle, CustomMessageBoxButtons.YesNo)).ShowDialog() == DialogResult.Yes)
            {
                LoadFile();
                hasDataChanges = false;
            }
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            var form = new AddWord(AddWord.AddWordWindowMode.Add, OnDataSaved);
            form.ShowDialog();
        }

        private void btnEdit_Click(object sender, EventArgs e)
        {
            if(lvWords.SelectedItems.Count == 0)
                return;
            if(lvWords.SelectedItems.Count > 1)
            {
                (new CustomMessageBox(FormMessages.MultipleSelectionForEdit, FormMessages.ErrorMessageTitle,
                                      CustomMessageBoxButtons.Cancel)).ShowDialog();
                return;
            }

            var selection = (VocabularyWord)((CustomListViewItem) lvWords.SelectedItems[0]).AdditionalInfo[VocabularyWordConst.VocabularyWord];
            var form = new AddWord(AddWord.AddWordWindowMode.Edit, OnDataSaved, selection);
            form.ShowDialog();
        }

        public void OnDataSaved(object sender, EventArgs e)
        {
            if (sender == null || !(sender is AddWord))
                return;

            var form = sender as AddWord;
            if (form.Mode == AddWord.AddWordWindowMode.Edit)
            {
                if (lvWords.SelectedItems.Count == 0)
                    return;
                
                //Save the new data            
                SaveData((CustomListViewItem)lvWords.SelectedItems[0], form);

                lvWords.SelectedItems[0].SubItems[0].Text = form.VocabWord.Word;
                lvWords.SelectedItems[0].SubItems[1].Text = form.VocabWord.Translation;
            }
            else //Add new
            {
                var item = new CustomListViewItem(new[] { form.VocabWord.Word, form.VocabWord.Translation });
                
                //Save the new data            
                SaveData(item, form);                               

                //Create a new item under the selected element in the list or ar the end of the list if no selection is available
                if (lvWords.SelectedItems.Count == 0)
                {
                    lvWords.Items.Add(item);
                }
                else
                {
                    var index = lvWords.SelectedItems[0].Index;
                    lvWords.Items.Insert(index + 1, item);                    
                }

                //Make sure that the item is visible
                item.EnsureVisible();
            }

            //Enable the test if the list has at least one word
            btnTest.Enabled = btnSort.Enabled = btnSortDesc.Enabled = lvWords.Items.Count > 0;

            //Mark the list as modified
            hasDataChanges = true;
        }

        protected static void SaveData(CustomListViewItem item, AddWord form)
        {
            if (!item.AdditionalInfo.ContainsKey(VocabularyWordConst.VocabularyWord))
                item.AdditionalInfo[VocabularyWordConst.VocabularyWord] = new VocabularyWord();

            var info = (VocabularyWord)item.AdditionalInfo[VocabularyWordConst.VocabularyWord];
            info.Word = form.VocabWord.Word;
            info.Translation = form.VocabWord.Translation;
            info.PartOfSpeech = form.VocabWord.PartOfSpeech;
            info.Examples = form.VocabWord.Examples;
            info.Remarks = form.VocabWord.Remarks;            
        }

        private void btnDelete_Click(object sender, EventArgs e)
        {
            if(lvWords.SelectedItems.Count == 0)
                return;
            
            if((new CustomMessageBox(FormMessages.ConfirmWordRemovalQuestion,FormMessages.DeleteFormTitle, CustomMessageBoxButtons.YesNo)).ShowDialog() == DialogResult.Yes)
            {
                foreach (ListViewItem item in lvWords.SelectedItems)
                {
                    lvWords.Items.Remove(item);   
                }

                //Enable the test if the list has at least one word
                btnTest.Enabled = btnSort.Enabled = btnSortDesc.Enabled = lvWords.Items.Count > 0;

                //Mark the list as modified
                hasDataChanges = true;                
            }
        }

        private void btnTest_Click(object sender, EventArgs e)
        {
            var allWords = new List<VocabularyWord>();
            foreach (CustomListViewItem item in lvWords.Items)
            {
                allWords.Add((VocabularyWord)item.AdditionalInfo[VocabularyWordConst.VocabularyWord]);
            }

            if(allWords.Count == 0)
            {
                (new CustomMessageBox(FormMessages.NoWordToTestMsg, FormMessages.NoWordToTestTitle,
                                      CustomMessageBoxButtons.OK)).ShowDialog();
                return;
            }

            var form = new VocabularyTest(allWords, FilePath );
            form.ShowDialog();
        }

        private void btnSort_Click(object sender, EventArgs e)
        {
            lvWords.Sorting = SortOrder.Ascending;
            lvWords.Sort();
            lvWords.Sorting = SortOrder.None;
        }

        private void btnSortDesc_Click(object sender, EventArgs e)
        {
            lvWords.Sorting = SortOrder.Descending;
            lvWords.Sort();
            lvWords.Sorting = SortOrder.None;
        }

        #endregion
        
        #region Context menu commands

        private void testVocabulary_Click(object sender, EventArgs e)
        {
            btnTest_Click(sender, e);
        }

        private void addWord_Click(object sender, EventArgs e)
        {
            btnAdd_Click(sender, e);
        }

        private void editWord_Click(object sender, EventArgs e)
        {
            btnEdit_Click(sender, e);
        }

        private void deleteWord_Click(object sender, EventArgs e)
        {
            btnDelete_Click(sender, e);
        }

        private void sortWords_Click(object sender, EventArgs e)
        {
            btnSort_Click(sender, e);
        }

        private void sortWordsDesc_Click(object sender, EventArgs e)
        {
            btnSortDesc_Click(sender, e);
        }
        
        private void btnSearch_Click(object sender, EventArgs e)
        {
            lvWords.Focus();
            var firstItemFound = false;

            foreach(ListViewItem item in lvWords.Items)
            {
                if (!firstItemFound && (item.SubItems[0].Text.ToLower().Contains(tbSearch.Text.ToLower()) ||
                                        item.SubItems[1].Text.ToLower().Contains(tbSearch.Text.ToLower())))
                {
                    item.Selected = true;
                    item.EnsureVisible();
                    firstItemFound = true;
                }
                else
                    item.Selected = (item.SubItems[0].Text.ToLower().Contains(tbSearch.Text.ToLower()) ||
                                     item.SubItems[1].Text.ToLower().Contains(tbSearch.Text.ToLower()));
            }
        }

        private void tbSearch_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode != Keys.Enter) 
                return;

            e.Handled = true;
            e.SuppressKeyPress = true;
            btnSearch_Click(sender, e);
        }

        private void tbSearch_DoubleClick(object sender, EventArgs e)
        {
            tbSearch.SelectAll();
        }

        #endregion
        
        #region Form Shortcuts & events

        private void Vocabulary_KeyDown(object sender, KeyEventArgs e)
        {
            if (!e.Control) return;

            switch (e.KeyCode)
            {
                case Keys.S:
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                    btnSave_Click(sender, e);                  
                    break;      
                case Keys.F:
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                    tbSearch.SelectAll();
                    tbSearch.Focus();
                    break;
            }
        }

        private void Test_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!hasDataChanges)
                return;

            if ((new CustomMessageBox(FormMessages.ConfirmWindowClosingQuestion, FormMessages.WindowClosingTitle, CustomMessageBoxButtons.YesNo)).ShowDialog() == DialogResult.No)
            {
                e.Cancel = true;
            }
        }

        #endregion
    }
}
